import dayjs, { Dayjs } from "dayjs";
export abstract class ListAutoWraper<Cooked, Raw> {
  map: Map<string, Cooked> = new Map<string, Cooked>();
  list: Cooked[] = [];
  private _ignore_order: boolean = false;
  abstract is_change(a: Raw, b: Raw): boolean;
  abstract cook(raw: Raw, now: Dayjs): Cooked;
  abstract raw(item: Cooked): Raw;
  abstract key(item: Raw | Cooked): string;
  protected _on_item_change?(item: Cooked, prev?: Cooked): void;
  protected _on_list_change?(cooked_list: Cooked[], prev_list: Cooked[]): void;
  ignore_order() {
    this._ignore_order = true;
    return this;
  }
  on_item_change(func: typeof this._on_item_change): this {
    this._on_item_change = func;
    return this;
  }
  on_list_change(func: typeof this._on_list_change): this {
    this._on_list_change = func;
    return this;
  }
  set_raw_list(raw_list: Raw[]) {
    const now = dayjs();
    let list_changed = false;
    let cooked_list: Cooked[] = raw_list.map<Cooked>((raw, idx) => {
      const key = this.key(raw);
      const prev = this.map.get(key);
      const old_cooked_item = this.list[idx];
      const order_changed = !this._ignore_order && old_cooked_item && this.key(old_cooked_item) !== key;
      if (prev && !this.is_change(raw, this.raw(prev))) {
        list_changed = list_changed || order_changed;
        return prev;
      }

      list_changed = true;
      const item = this.cook(raw, now);
      this.map.set(key, item)
      this._on_item_change?.(item, prev);
      return item;
    });
    list_changed = list_changed || this.list.length != cooked_list.length;
    if (!list_changed) return;
    const prev_list = this.list;
    this.list = cooked_list;
    this._on_list_change?.(cooked_list, prev_list);
  }
}
